package com.zhang.common.secret;

import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

/**
 * 数据加密和解密，加密算法有MD5、DES、RSA、报文加密，此外包括Base64字符转换
 * @author zhangxin
 * @date 2021/6/28
 */
@Slf4j
public class ICodeCipher {
//	/* 报文编码(Quoted Printable Codec) */
//	private static QuotedPrintableCodec qp = new QuotedPrintableCodec("GBK");
	/* DES加密 */
	private final static String DES = "DES";
	/* RSA加密 */
	private final static String RSA = "RSA";
	/** MD5加密 */
	public final static String MD5 = "MD5";
	/** SHA-1加密 */
	public final static String SHA_1 = "SHA-1";
	/** SHA1withRSA加密 */
	public final static String SHA1withRSA = "SHA1withRSA";

	/**
	 * 使用DES算法对给定数据进行加密
	 * 
	 * @param rowData
	 *            原数据
	 * @param rawKeyData
	 *            密钥，必须为8位字节
	 * @return 密文
	 * @throws Exception
	 */
	public static byte[] encryptDES(byte[] rowData, byte[] rawKeyData) throws Exception {
		// 从原始密钥数据创建DESKeySpec对象
		DESKeySpec dks = new DESKeySpec(rawKeyData);
		// 创建一个密钥工厂，然后用它把DESKeySpec转换成Secret Key对象
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
		SecretKey key = keyFactory.generateSecret(dks);
		// Cipher对象实际完成加密操作
		Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
		// 初始向量
		// 初始向量
		byte[] biv = new byte[8];
		for(int i = 0; i < 8 && i < rawKeyData.length; i++) {
			biv[i] = rawKeyData[i];
		}
		IvParameterSpec iv = new IvParameterSpec(biv);
		// 用密钥初始化Cipher对象
		cipher.init(Cipher.ENCRYPT_MODE, key, iv);
		// 执行加密操作
		return cipher.doFinal(rowData);
	}

	/**
	 * 对给定加密后的数据进行DES解密
	 * 
	 * @param encryptData
	 *            加密后的数据
	 * @param rawKeyData
	 *            密钥，必须为8位字节
	 * @return 解密后的明文
	 * @throws Exception
	 */
	public static byte[] decryptDES(byte[] encryptData, byte[] rawKeyData) throws Exception {
		// 创建一个DESKeySpec对象
		DESKeySpec dks = new DESKeySpec(rawKeyData);
		// 创建一个密钥工厂，然后用它把DESKeySpec对象转换成Secret Key对象
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
		SecretKey key = keyFactory.generateSecret(dks);
		// Cipher对象实际完成解密操作
		Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
		// 初始向量
		byte[] biv = new byte[8];
		for(int i = 0; i < 8 && i < rawKeyData.length; i++) {
			biv[i] = rawKeyData[i];
		}
		IvParameterSpec iv = new IvParameterSpec(biv);
		// 用密钥初始化Cipher对象
		cipher.init(Cipher.DECRYPT_MODE, key, iv);
		// 执行解密操作
		return cipher.doFinal(encryptData);
	}

	/**
	 * 获得RSA密钥对
	 * 
	 * @return RSA密钥对，内含公钥和私钥。
	 * @throws NoSuchAlgorithmException
	 */
	public static KeyPair getRSAKeyPair() throws NoSuchAlgorithmException {
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA);

		// 密钥位数
		keyPairGen.initialize(1024);

		// 密钥对
		return keyPairGen.generateKeyPair();
	}

	/**
	 * 使用钥匙的模数和指数还原公钥
	 * 
	 * @param modulus
	 *            公钥模数
	 * @param publicExponent
	 *            公钥指数
	 * 
	 * @return 还原后的公钥
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static PublicKey recoverRSAPublicKey(BigInteger modulus, BigInteger publicExponent)
			throws NoSuchAlgorithmException, InvalidKeySpecException {
		KeyFactory keyFac = KeyFactory.getInstance(RSA);

		RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
		return keyFac.generatePublic(pubKeySpec);
	}

	/**
	 * 使用钥匙的模数和指数还原私钥
	 * 
	 * @param modulus
	 *            私钥模数
	 * @param privateExponent
	 *            私钥指数
	 * @return 还原后的私钥
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static PrivateKey recoverRSAPrivateKey(BigInteger modulus, BigInteger privateExponent)
			throws NoSuchAlgorithmException, InvalidKeySpecException {
		KeyFactory keyFac = KeyFactory.getInstance(RSA);

		RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(modulus, privateExponent);
		return keyFac.generatePrivate(priKeySpec);
	}

	/**
	 * 使用RSA算法对给定数据进行加密
	 * 
	 * @param rowData
	 *            原数据
	 * @param publicKey
	 *            用于加密的公钥
	 * @return 密文
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] encryptRSA(byte[] rowData, PublicKey publicKey)
			throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException {
		// 加解密类
		Cipher cipher = Cipher.getInstance(RSA);

		// 用密钥初始化Cipher对象
		cipher.init(Cipher.ENCRYPT_MODE, publicKey);

		// 执行加密操作
		return cipher.doFinal(rowData);
	}

	/**
	 * 对给定加密后的数据进行DES解密
	 * 
	 * @param encryptData
	 *            加密后的数据
	 * @param privateKey
	 *            解密的私钥
	 * @return 解密后的明文
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeySpecException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] decryptRSA(byte[] encryptData, PrivateKey privateKey)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
			IllegalBlockSizeException, BadPaddingException {
		// Cipher对象实际完成解密操作
		Cipher cipher = Cipher.getInstance(RSA);
		// 用密钥初始化Cipher对象
		cipher.init(Cipher.DECRYPT_MODE, privateKey);
		// 执行解密操作
		return cipher.doFinal(encryptData);
	}

	/**
	 * 使用私钥对数据进行签名
	 * 
	 * @param privateKey
	 *            私钥
	 * @param data
	 *            待签名数据
	 * @param algorithm
	 *            算法，支持SHA1withRSA
	 * @return 签名
	 * 
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 */
	public static byte[] sign(PrivateKey privateKey, byte[] data, String algorithm)
			throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		byte[] result = null;
		Signature st = Signature.getInstance(algorithm);
		st.initSign(privateKey);
		st.update(data);
		result = st.sign();
		return result;
	}

	/**
	 * 使用公钥验证签名
	 * 
	 * @param publicKey
	 *            公钥
	 * @param srcData
	 *            源数据
	 * @param signData
	 *            签名数据
	 * @param algorithm
	 *            算法，支持SHA1withRSA
	 * @return true：成功 false：失败
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 * @throws Exception
	 */
	public static boolean verifySign(PublicKey publicKey, byte[] srcData, byte[] signData, String algorithm)
			throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		Signature st = Signature.getInstance(algorithm);
		st.initVerify(publicKey);
		st.update(srcData);
		return st.verify(signData);
	}

	/**
	 * 对给定的字节数组进行BASE64编码
	 * 
	 * @param bt
	 *            需要进行BASE64编码的字节数组
	 * @return 经BASE64编码后得到的字符串
	 */
	public static String encodeBASE64(byte[] bt) {
		if (bt == null || bt.length == 0)
			return null;
		return (new BASE64Encoder()).encode(bt);
	}

//	/**
//	 * quoted-printable解码
//	 *
//	 * @param str
//	 *            quoted-printable编码的字符串
//	 * @return quoted-printable解码后的数据
//	 */
//	public static String decodeQuotedPrintable(String str) {
//		try {
//			return qp.decode(str);
//		} catch (Exception ex) {
//		}
//
//		return null;
//	}

//	/**
//	 * quoted-printable编码
//	 *
//	 * @param str
//	 *            待进行quoted-printable编码的字符串
//	 * @return quoted-printable编码后的数据
//	 */
//	public static String encodeQuotedPrintable(String str) {
//		try {
//			return qp.encode(str);
//		} catch (Exception ex) {
//		}
//
//		return null;
//	}

	/**
	 * 对给定的BASE64编码进行解码
	 * 
	 * @param str
	 *            BASE64编码字符串
	 * @return 经BASE64解码后得到的字节数组
	 */
	public static byte[] decodeBASE64(String str) {
		if (str == null || str.length() == 0)
			return null;
		byte[] bt = null;
		try {
			bt = (new BASE64Decoder()).decodeBuffer(str);
		} catch (Exception ex) {
		}

		return bt;
	}

	/**
	 * 使用消息摘要加密字节数组，消息摘要加密算法包括SHA-1、MD5
	 * 
	 * @param bts
	 *            要加密的字节数组
	 * @param algorithm
	 *            消息摘要加密算法
	 * @return 经消息摘要加密后的密文
	 */
	public static byte[] digest(byte[] bts, String algorithm) {
		if (bts == null)
			bts = new byte[0];
		byte byEncrypt[] = null;
		try {
			MessageDigest md = MessageDigest.getInstance(algorithm);
			md.update(bts);
			byEncrypt = md.digest();
		} catch (Exception ex) {
		}

		return byEncrypt;
	}

	/**
	 * 把二进制数据变为十六进制的字符串。将摘要转为字符串
	 * 
	 * @param data
	 *            二进制数组
	 * @return 十六进制的字符串
	 */
	public static String byte2hex(byte[] data) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			int n = data[i] & 0x00FF;
			if (n < 16) {
				sb.append("0");
			}

			sb.append(Integer.toHexString(n));
		}
		return sb.toString();
	}

	/**
	 * 把十六进制的字符串变为二进制数据。
	 * 
	 * @param value
	 *            十六进制的字符串
	 * 
	 * @return 二进制数组
	 */
	public static byte[] hex2byte(String value) {
		if (value == null || value.length() == 0) {
			return null;
		}

		if (value.length() % 2 == 1) {
			return null;
		}

		// 2位字符表示一个字节
		int nLen = value.length() / 2;
		byte[] bts = new byte[nLen];

		try {

			for (int i = 0; i < nLen; i++) {
				String s = value.substring(i * 2, i * 2 + 2);
				int n = Integer.parseInt(s, 16);

				bts[i] = (byte) (n & 0xFF);
			}
		} catch (Exception ex) {
			// ex.printStackTrace();
			return null;
		}

		return bts;
	}

	/**
	 * MD5算法
	 * @param sourceStr
	 * @return
	 */
	public static String MD5(String sourceStr) {
		String result = "";
		try {
			// 得到一个md5的消息摘要
			MessageDigest md = MessageDigest.getInstance("MD5");
			// 添加要进行计算摘要的信息
			md.update(sourceStr.getBytes());
			// 得到该摘要
			byte b[] = md.digest();
			int i;
			StringBuffer buf = new StringBuffer("");
			for (int offset = 0; offset < b.length; offset++) {
				i = b[offset];
				if (i < 0)
					i += 256;
				if (i < 16)
					buf.append("0");
				buf.append(Integer.toHexString(i));
			}
			result = buf.toString();
		} catch (NoSuchAlgorithmException e) {
			log.error("Message={}", e.getMessage(), "Cause={}", e.getCause());
		}
		return result;
	}

	public static void main(String[] args){
		System.out.println(MD5("732491729GL*FiovgSgYSlAhgh$uDez2z3lMGhv9v5URd"));
	}

}
